//
// Created by 33145 on 2023/6/8.
//

#include "tinypb_coder.h"
#include "../common/rocket.h"

#include <arpa/inet.h>  // 大小端转换
#include <cstring>

namespace rocket {

  // 将 message 对象转化为字节流，写入到 buffer
  void TinyPBCoder::encode(std::vector<AbstractProtocol::ptr> &messages, TCPBuffer::ptr out_buffer) {
    for (auto &i : messages) {
      TinyPBProtocol::ptr msg = std::dynamic_pointer_cast<TinyPBProtocol>(i);
      int len = 0;
      const char* buf = encodeTinyPB(msg, len);
      if (buf != nullptr && len != 0) {
        out_buffer->writeToBuffer(buf, len);
      }
      if (buf) {  // TODO 为什么要频繁make和free。。。
        free((void*)buf);
        buf = nullptr;
      }

    }
  }

  void TinyPBCoder::decode(std::vector<AbstractProtocol::ptr> &out_messages, TCPBuffer::ptr buffer) {
    while(true) {
      // 遍历 buffer，找到 PB_START，找到之后，解析出整包的长度。然后得到结束符的位置，判断是否为 PB_END
      std::vector<char> tmp = buffer->getBuffer();
      int start_index = buffer->readIndex();
      int end_index = -1;

      int pk_len = 0;
      bool parse_success = false;
      int i = 0;
      for (i = start_index; i < buffer->writeIndex(); ++i) {
        if (tmp[i] == TinyPBProtocol::PB_START) {
          // 读下去四个字节。由于是网络字节序，需要转为主机字节序
          // TODO 这里既然要读4个字节，那只判断一个+1就完备了吗？
          if (i + 1 < buffer->writeIndex()) {
            pk_len = getInt32FromNetByte(&tmp[i+1]);
            LOG_DEBUG("get pk_len = %d", pk_len);

            // 结束符的索引
            int j = i + pk_len - 1;
            if (j >= buffer->writeIndex()) {
              continue;  // 这里continue是不是应该返回整个大的while？ 是不是应该break？
            }
            if (tmp[j] == TinyPBProtocol::PB_END) {
              start_index = i;
              end_index = j;
              parse_success = true;
              break;
            }
          }
        }
      }  // 把消息的开始和结束的索引解析出来

      if (i >= buffer->writeIndex()) {
        LOG_DEBUG("decode end, read all buffer data");
        return;
      }
      // 开始符(0x02) | 整包长度(4) | MsgID长度(4) | MsgID | 方法名长度(4) | 方法名 | 错误码(4) |
      // 错误信息长度(4) | 错误信息 | Protobuf序列化数据 | 校验码(4) | 结束符（0x03）
      if (parse_success) {
        buffer->moveReadIndex(end_index - start_index + 1);
        TinyPBProtocol::ptr message = std::make_shared<TinyPBProtocol>();
        message->_pk_len = pk_len;
        // ----------------------------------------开始符+整包长度
        int msg_id_len_index = start_index + sizeof(char) + sizeof(message->_pk_len);
        if (msg_id_len_index >= end_index) {
          message->parse_success = false;
          LOG_ERROR("parse error, msg_id_len_index[%d] >= end_index[%d]", msg_id_len_index, end_index);
          continue;
        }
        message->_msg_id_len = getInt32FromNetByte(&tmp[msg_id_len_index]);
        LOG_DEBUG("parse msg_id_len=%d", message->_msg_id_len);
        // ----------------------------------------ID长度index + 4
        int msg_id_index = msg_id_len_index + sizeof(message->_msg_id_len);

        char msg_id[100] = {0};
        memcpy(&msg_id[0], &tmp[msg_id_index], message->_msg_id_len);
        message->_msg_id = std::string(msg_id);
        LOG_DEBUG("parse msg_id=%s", message->_msg_id.c_str());

        int method_name_len_index = msg_id_index + message->_msg_id_len;
        if (method_name_len_index >= end_index) {
          message->parse_success = false;
          LOG_ERROR("parse error, method_name_len_index[%d] >= end_index[%d]", method_name_len_index, end_index);
          continue;
        }
        message->_method_name_len = getInt32FromNetByte(&tmp[method_name_len_index]);

        int method_name_index = method_name_len_index + sizeof(message->_method_name_len);
        char method_name[512] = {0};
        memcpy(&method_name[0], &tmp[method_name_index], message->_method_name_len);
        message->_method_name = std::string(method_name);
        LOG_DEBUG("parse method_name=%s", message->_method_name.c_str());

        int err_code_index = method_name_index + message->_method_name_len;
        if (err_code_index >= end_index) {
          message->parse_success = false;
          LOG_ERROR("parse error, err_code_index[%d] >= end_index[%d]", err_code_index, end_index);
          continue;
        }
        message->_err_code = getInt32FromNetByte(&tmp[err_code_index]);


        int error_info_len_index = err_code_index + sizeof(message->_err_code);
        if (error_info_len_index >= end_index) {
          message->parse_success = false;
          LOG_ERROR("parse error, error_info_len_index[%d] >= end_index[%d]", error_info_len_index, end_index);
          continue;
        }
        message->_err_info_len = getInt32FromNetByte(&tmp[error_info_len_index]);

        int err_info_index = error_info_len_index + sizeof(message->_err_info_len);
        char error_info[512] = {0};
        memcpy(&error_info[0], &tmp[err_info_index], message->_err_info_len);
        message->_err_info = std::string(error_info);
        LOG_DEBUG("parse error_info=%s", message->_err_info.c_str());

        int pb_data_len = message->_pk_len - message->_method_name_len - message->_msg_id_len - message->_err_info_len - 2 - 24;

        int pd_data_index = err_info_index + message->_err_info_len;
        message->_pb_data = std::string(&tmp[pd_data_index], pb_data_len);

        // 这里校验和去解析
        message->parse_success = true;

        out_messages.push_back(message);
      }

    }
  }
  // 开始符(0x02) | 整包长度(4) | MsgID长度(4) | MsgID | 方法名长度(4) | 方法名 | 错误码(4) |
  // 错误信息长度(4) | 错误信息 | Protobuf序列化数据 | 校验码(4) | 结束符（0x03）
  const char *TinyPBCoder::encodeTinyPB(const TinyPBProtocol::ptr& message, int& len) {
    if (message->_msg_id.empty()) {
      message->_msg_id = "123456789";
    }
    LOG_DEBUG("msg_id = %s", message->_msg_id.c_str());
    // 开始+结束 = 2  各种长度 = 24 + 其他
    int pk_len = 2 + 24 + message->_msg_id.length() + message->_method_name.length() + message->_err_info.length() + message->_pb_data.length();
    LOG_DEBUG("pk_len = %", pk_len);

    char* buf = reinterpret_cast<char*>(malloc(pk_len));
    char* tmp = buf;

    *tmp = TinyPBProtocol::PB_START;
    tmp++;

    int32_t pk_len_net = htonl(pk_len);
    memcpy(tmp, &pk_len_net, sizeof(pk_len_net));
    tmp += sizeof(pk_len_net);

    int msg_id_len = message->_msg_id.length();
    int32_t msg_id_len_net = htonl(msg_id_len);
    memcpy(tmp, &msg_id_len_net, sizeof(msg_id_len_net));
    tmp += sizeof(msg_id_len_net);

    if (!message->_msg_id.empty()) {
      memcpy(tmp, &(message->_msg_id[0]), msg_id_len);
      tmp += msg_id_len;
    }

    int method_name_len = message->_method_name.length();
    int32_t method_name_len_net = htonl(method_name_len);
    memcpy(tmp, &method_name_len_net, sizeof(method_name_len_net));
    tmp += sizeof(method_name_len_net);

    if (!message->_method_name.empty()) {
      memcpy(tmp, &(message->_method_name[0]), method_name_len);
      tmp += method_name_len;
    }

    int32_t err_code_net = htonl(message->_err_code);
    memcpy(tmp, &err_code_net, sizeof(err_code_net));
    tmp += sizeof(err_code_net);

    int err_info_len = message->_err_info.length();
    int32_t err_info_len_net = htonl(err_info_len);
    memcpy(tmp, &err_info_len_net, sizeof(err_info_len_net));
    tmp += sizeof(err_info_len_net);

    if (!message->_err_info.empty()) {
      memcpy(tmp, &(message->_err_info[0]), err_info_len);
      tmp += err_info_len;
    }

    if (!message->_pb_data.empty()) {
      memcpy(tmp, &(message->_pb_data[0]), message->_pb_data.length());
      tmp += message->_pb_data.length();
    }

    int32_t check_su_net = htonl(1);
    memcpy(tmp, &check_su_net, sizeof(check_su_net));
    tmp += sizeof(check_su_net);

    *tmp = TinyPBProtocol::PB_END;

    message->_pk_len = pk_len;
    message->_msg_id_len = msg_id_len;
    message->_method_name_len = method_name_len;
    message->_err_info_len = err_info_len;
    message->parse_success = true;
    len = pk_len;

    LOG_DEBUG("encode message[%s] success", message->_msg_id.c_str());

    return buf;
  }
}